package com.dick.base.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dick.base.dto.*;
import com.dick.base.exception.BaseRuntimeException;
import com.dick.base.exception.DataStateNotAllowedException;
import com.dick.base.exception.ResourceNotFoundException;
import com.dick.base.mapper.*;
import com.dick.base.model.*;
import com.dick.base.parameter.*;
import com.dick.base.util.DateUtil;
import com.dick.base.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 生产过程相关服务
 */
@Service
public class ProduceService {

    @Autowired
    private BusinessOrderMapper businessOrderMapper;
    @Autowired
    private BusinessOrderItemMapper businessOrderItemMapper;
    @Autowired
    private BusinessProjectMapper businessProjectMapper;
    @Autowired
    private BusinessCustomerMapper businessCustomerMapper;
    @Autowired
    private BusinessBoardMapper businessBoardMapper;
    @Autowired
    private ProduceItemMapper produceItemMapper;
    @Autowired
    private BusinessRollMapper businessRollMapper;
    @Autowired
    private ProduceShelfMapper produceShelfMapper;

    /**
     * 生产项列表
     * @return
     */
    @Transactional
    public ToBeProduceList produceList(String material, String thickness, Integer width, Integer status, Long customerId) {
        ToBeProduceList toBeProduceList = new ToBeProduceList();
        ProduceItem itemSelect = new ProduceItem();
        itemSelect.setStatus(status);
        if (!StringUtils.isEmpty(material)) {
            itemSelect.setMaterial(material);
        }
        if (!StringUtils.isEmpty(thickness)) {
            itemSelect.setThickness(thickness);
        }
        if (width != null) {
            itemSelect.setWidth(width);
        }
        QueryWrapper<ProduceItem> queryWrapper = new QueryWrapper<>(itemSelect);
        if (customerId != null) {
            BusinessProject projectSelect = new BusinessProject();
            projectSelect.setCustomerId(customerId);
            List<BusinessProject> projectList = businessProjectMapper.selectList(new QueryWrapper<>(projectSelect));
            if (projectList == null || projectList.isEmpty()) {
                itemSelect.setStatus(-9999); //强行查询不到数据
            } else {
                queryWrapper.in("project_id",
                        projectList.stream().map(BusinessProject::getId).collect(Collectors.toSet()));
            }
        }
        List<ProduceItem> itemList = produceItemMapper.selectList(queryWrapper);
        if (itemList.isEmpty()) {
            return toBeProduceList;
        }
        Map<String, ToBeProduceList.ProjectInfo> projectInfoMap = new HashMap<>();
        itemList.stream().forEach(item -> {
            ToBeProduceList.ProjectItem projectItem = new ToBeProduceList.ProjectItem();
            projectItem.setId(item.getId());
            projectItem.setMaterial(item.getMaterial());
            projectItem.setThickness(item.getThickness());
            projectItem.setWidth(item.getWidth());
            projectItem.setWeight(item.getWeight());
            projectItem.setQuantity(item.getQuantity());
            projectItem.setFilm(item.getFilm());
            projectItem.setLength(item.getLength());
            projectItem.setChangeSpecification(item.getChangeSpecification());
            String virtualKey = projectVirtualKey(item);
            ToBeProduceList.ProjectInfo projectInfo = projectInfoMap.get(projectVirtualKey(item));
            if (projectInfo == null) {
                projectInfo = new ToBeProduceList.ProjectInfo();
                projectInfo.setCustomerNickname(item.getCustomerNickname());
                projectInfo.setProjectName(item.getProjectName());
                projectInfo.setProjectBatchNumber(item.getProjectBatchNumber());
                projectInfo.setProjectVirtualId(virtualKey);
                projectInfoMap.put(projectInfo.getProjectVirtualId(), projectInfo);
                toBeProduceList.getProjects().add(projectInfo);
            }
            projectInfo.getItemList().add(projectItem);
            projectInfo.setItemCount(projectInfo.getItemList().size());
            projectInfo.setWeight(projectInfo.getWeight().add(item.getWeight()));
        });
        return toBeProduceList;
    }

    /**
     * 填充备注和紧急程度
     * @param toBeProduceList
     */
    public ToBeProduceList fillInMemoAndUrgent(ToBeProduceList toBeProduceList) {
        if (!toBeProduceList.getProjects().isEmpty()) {
            List<BusinessOrder> orders = businessOrderMapper.selectList(new QueryWrapper<BusinessOrder>()
                    .in("project_id", toBeProduceList.getProjects().stream().map(pi -> pi.getProjectVirtualId().split("_")[0]).collect(Collectors.toSet()))
            );
            Map<String, BusinessOrder> virtualKeyToOrderMap = new HashMap<>(orders.size() * 2 + 1);
            orders.stream().forEach(o -> {
                virtualKeyToOrderMap.put(this.projectVirtualKey(o), o);
            });
            toBeProduceList.getProjects().stream().forEach(pi -> {
                BusinessOrder order = virtualKeyToOrderMap.get(pi.getProjectVirtualId());
                if (order != null) {
                    pi.setUrgent(order.getUrgent());
                    pi.setMemo(order.getMemo());
                }
            });
        }
        return toBeProduceList;
    }

    /**
     * 工程批次组合key
     * @return
     */
    private String projectVirtualKey(BusinessOrder order) {
        return String.format("%d_%s", order.getProjectId(), order.getBatchNumber());
    }

    /**
     * 规格组合key
     * @return
     */
    private String specificationGroupKey(String material, String thickness, Integer width) {
        return String.format("%s_%s_%d", material, thickness, width);
    }

    /**
     * 添加加工列表
     * <pre>
     *     1.查看是否满足添加条件：当前用户只能添加一种规格，被添加数据状态为待加工
     *     2.执行添加：更新生产数据表，更新订单数据表，更新订单状态
     * </pre>
     * @param addProduceIdList 订单项id列表
     */
    @Transactional
    public void addProduceId(ProduceIdList addProduceIdList, Long userId) {
        ProduceItem itemSelect = new ProduceItem();
        itemSelect.setUserId(userId);
        itemSelect.setStatus(ProduceItem.STATUS_PRODUCING);
        Page<ProduceItem> page = produceItemMapper.selectPage(
                new Page<>(1, 1, false), new QueryWrapper<>(itemSelect));
        Set<String> specificationGroupKeys = new HashSet<>(3);
        int baseIndex = 0;
        if (!page.getRecords().isEmpty()) {
            ProduceItem produceItem = page.getRecords().get(0);
            specificationGroupKeys.add(specificationGroupKey(produceItem.getMaterial(), produceItem.getThickness(),
                    produceItem.getWidth()));
            baseIndex = page.getRecords().stream().mapToInt(ProduceItem::getItemIndex).max().getAsInt() + 1;
//            BusinessOrderItem orderItem = businessOrderItemMapper.selectById(addProduceIdList.getIdList().get(0));
//            if (!specificationGroupKey(produceItem.getMaterial(), produceItem.getThickness(), produceItem.getWidth())
//                    .equals(specificationGroupKey(orderItem.getMaterialActual(), orderItem.getThicknessActual(),
//                            orderItem.getWidthActual()))) {
//                throw new BaseRuntimeException("添加失败，已添加的生产列表存在不同规格订单项，请先生产完毕。", HttpStatus.BAD_REQUEST);
//            }
        }

        Map<Long, Integer> idToIndexMap = new HashMap<>(addProduceIdList.getIdList().size() * 2 + 1);
        for (int i = 0; i < addProduceIdList.getIdList().size(); i ++) {
            idToIndexMap.put(addProduceIdList.getIdList().get(i), baseIndex + i);
        }
        List<ProduceItem> orderItemList = produceItemMapper.selectBatchIds(addProduceIdList.getIdList());
        Optional<ProduceItem> optional = orderItemList.stream()
                .filter(item -> item.getStatus() > ProduceItem.STATUS_TO_BE_PRODUCED).findAny();
        if (optional.isPresent()) {
            throw new BaseRuntimeException("添加失败，数据过时，请刷新列表数据。", HttpStatus.BAD_REQUEST);
        }

        Set<Long> addIds = new HashSet<>(addProduceIdList.getIdList().size() * 2 + 1);
        Set<Long> orderIds = new HashSet<>();
        List<ProduceItem> produceItems = orderItemList.stream().map(orderItem -> {
            String newKey = specificationGroupKey(orderItem.getMaterial(), orderItem.getThickness(),
                    orderItem.getWidth());
            if (!specificationGroupKeys.isEmpty() && !specificationGroupKeys.contains(newKey)) {
                return null;
            } else {
                specificationGroupKeys.add(newKey);
            }
            ProduceItem produceItem = new ProduceItem();
            produceItem.setId(orderItem.getId());
            produceItem.setUpdateTime(DateUtil.now());
            produceItem.setItemIndex(idToIndexMap.get(orderItem.getId()));
            produceItem.setUserId(userId);
            produceItem.setStatus(ProduceItem.STATUS_PRODUCING);
            addIds.add(produceItem.getId());
            orderIds.add(orderItem.getOrderId());
            return produceItem;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        if (addIds.isEmpty()) {
            throw new BaseRuntimeException("添加失败，请尝试先生产已添加生产的订单项");
        }
        produceItems.stream().forEach(produceItemMapper::updateById);
        BusinessOrderItem businessOrderItemUpdate = new BusinessOrderItem();
        businessOrderItemUpdate.setStatus(BusinessOrderItem.STATUS_PRODUCING);
        businessOrderItemUpdate.setUpdateTime(DateUtil.now());
        businessOrderItemMapper.update(businessOrderItemUpdate,
                new QueryWrapper<BusinessOrderItem>().in("id", addIds)
        );
        orderIds.stream().forEach(businessOrderMapper::updateStatus);
    }

    /**
     * 获取某人的当前加工列表
     * @return
     */
    public ToBeProduceList produceList(Long userId) {
        ToBeProduceList toBeProduceList = new ToBeProduceList();
        ProduceItem itemSelect = new ProduceItem();
        itemSelect.setUserId(userId);
        itemSelect.setStatus(ProduceItem.STATUS_PRODUCING);
        List<ProduceItem> itemList = produceItemMapper.selectList(new QueryWrapper<>(itemSelect));
        Map<String, ToBeProduceList.ProjectInfo> projectInfoMap = new LinkedHashMap<>();
        itemList.stream().forEach(item -> {
            String virtualId = projectVirtualKey(item);
            ToBeProduceList.ProjectInfo projectInfo = projectInfoMap.get(virtualId);
            if (projectInfo == null) {
                projectInfo = new ToBeProduceList.ProjectInfo();
                projectInfo.setProjectVirtualId(virtualId);
                projectInfo.setProjectName(item.getProjectName());
                projectInfo.setProjectBatchNumber(item.getProjectBatchNumber());
                projectInfo.setCustomerNickname(item.getCustomerNickname());
                projectInfoMap.put(virtualId, projectInfo);
            }
            ToBeProduceList.ProjectItem projectItem = new ToBeProduceList.ProjectItem();
            projectItem.setId(item.getId());
            projectItem.setMaterial(item.getMaterial());
            projectItem.setThickness(item.getThickness());
            projectItem.setWidth(item.getWidth());
            projectItem.setLength(item.getLength());
            projectItem.setQuantity(item.getQuantity());
            projectItem.setWeight(item.getWeight());
            projectItem.setFilm(item.getFilm());
            projectItem.setChangeSpecification(item.getChangeSpecification());
            projectItem.setIndex(item.getItemIndex());
            projectInfo.getItemList().add(projectItem);
            projectInfo.setItemCount(projectInfo.getItemList().size());
            projectInfo.setWeight(projectInfo.getWeight().add(item.getWeight()) );
        });
        toBeProduceList.getProjects().addAll(projectInfoMap.values());
        projectInfoMap.values().forEach(p -> {
            Collections.sort(p.getItemList(), Comparator.comparingInt(ToBeProduceList.ProjectItem::getIndex));
        });
        toBeProduceList.getProjects().stream().forEach(p -> {
            p.setIndex(p.getItemList().stream().mapToInt(ToBeProduceList.ProjectItem::getIndex).min().getAsInt());
        });
        if (!toBeProduceList.getProjects().isEmpty()) {
            Collections.sort(toBeProduceList.getProjects(),
                    Comparator.comparingInt(ToBeProduceList.ProjectInfo::getIndex)
            );
        }
        return toBeProduceList;
    }

    /**
     * 工程批次组合key
     * @return
     */
    private String projectVirtualKey(ProduceItem item) {
        return projectVirtualKey(item.getProjectId(), item.getProjectBatchNumber());
    }

    /**
     * 工程批次组合key
     * @return
     */
    private String projectVirtualKey(ProduceShelf shelf) {
        return projectVirtualKey(shelf.getProjectId(), shelf.getBatchNumber());
    }

    private String projectVirtualKey(Long projectId, String batchNumber) {
        return String.format("%d_%s", projectId, batchNumber);
    }

    /**
     * 加工完成，操作幂等
     * <pre>
     *     1.更新生产数据状态
     *     2.更新订单数据状态
     *     3.更新订单状态
     * </pre>
     */
    @Transactional(rollbackFor = Exception.class)
    public void produceComplete(ProduceIdList produceIdList, Long userId) {

        QueryWrapper<ProduceItem> produceItemQueryWrapper = new QueryWrapper<ProduceItem>();
        produceItemQueryWrapper.eq("user_id", userId);
        produceItemQueryWrapper.eq("status", ProduceItem.STATUS_PRODUCING);
        produceItemQueryWrapper.in("id", produceIdList.getIdList());
        produceItemQueryWrapper.select("id", "order_id");
        List<ProduceItem> produceItems = produceItemMapper.selectList(produceItemQueryWrapper);
        if (produceItems.isEmpty()) {
            return;
        }
        Set<Long> itemIdSet = produceItems.stream().map(ProduceItem::getId).collect(Collectors.toSet());
        ProduceItem produceItemUpdate = new ProduceItem();
        produceItemUpdate.setStatus(ProduceItem.STATUS_PRODUCED);
        produceItemUpdate.setUpdateTime(DateUtil.now());
        produceItemMapper.update(produceItemUpdate, new QueryWrapper<ProduceItem>().in("id", itemIdSet));

        BusinessOrderItem businessOrderItemUpdate = new BusinessOrderItem();
        businessOrderItemUpdate.setUpdateTime(DateUtil.now());
        businessOrderItemUpdate.setStatus(BusinessOrderItem.STATUS_PRODUCED);
        businessOrderItemMapper.update(businessOrderItemUpdate, new QueryWrapper<BusinessOrderItem>().in("id", itemIdSet));

        Set<Long> orderIdSet = produceItems.stream().map(ProduceItem::getOrderId).collect(Collectors.toSet());
//        List<OrderNotProduced> orderNotProducedList = businessOrderItemMapper.findOrderNotProduced(orderIdSet);
//        Set<Long> orderProduced = new HashSet<>(orderIdSet);
//        orderProduced.removeAll(
//                orderNotProducedList.stream().filter(p -> p.getQuantity() > 0)
//                        .map(OrderNotProduced::getOrderId).collect(Collectors.toSet())
//        );
//        if (!orderProduced.isEmpty()) {
//            BusinessOrder businessOrderUpdate = new BusinessOrder();
//            businessOrderUpdate.setStatus(BusinessOrder.STATUS_PRODUCED);
//            businessOrderMapper.update(businessOrderUpdate, new QueryWrapper<BusinessOrder>().in("id", orderProduced));
//        }
        orderIdSet.stream().forEach(businessOrderMapper::updateStatus);
    }

    /**
     * 根据订单项插入生产项
     * @param orderItemList
     */
    public void insertProduceItem(List<BusinessOrderItem> orderItemList, BusinessOrder businessOrder) {
        BusinessProject businessProject = businessProjectMapper.selectById(businessOrder.getProjectId());
        BusinessCustomer businessCustomer = businessCustomerMapper.selectById(businessOrder.getCustomerId());
        Optional<BusinessOrderItem> optional = orderItemList.stream()
                .filter(item -> item.getStatus() > BusinessOrderItem.STATUS_TO_BE_PRODUCED).findAny();
        if (optional.isPresent()) {
            throw new BaseRuntimeException("添加失败，存在不允许添加的订单项。", HttpStatus.BAD_REQUEST);
        }
        List<ProduceItem> produceItems = orderItemList.stream().map(orderItem -> {
            ProduceItem produceItem = new ProduceItem();
            produceItem.setId(orderItem.getId());
            produceItem.setCreateTime(DateUtil.now());
            produceItem.setItemIndex(0);
            produceItem.setChangeSpecification(orderItem.getChangeSpecification());
            produceItem.setLength(orderItem.getLength());
            produceItem.setFilm(orderItem.getFilm());
            produceItem.setMaterial(orderItem.getMaterialActual());
            produceItem.setStatus(orderItem.getStatus());
            produceItem.setQuantity(orderItem.getQuantity());
            produceItem.setThickness(orderItem.getThicknessActual());
            produceItem.setWeight(orderItem.getWeight());
            produceItem.setWidth(orderItem.getWidthActual());
            if (businessProject != null && businessCustomer != null) {
                produceItem.setOrderId(businessOrder.getId());
                produceItem.setProjectId(businessOrder.getProjectId());
                produceItem.setProjectBatchNumber(businessOrder.getBatchNumber());
                produceItem.setProjectName(businessProject.getName());
                produceItem.setCustomerNickname(businessCustomer.getNickname());
                return produceItem;
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        produceItems.stream().forEach(produceItemMapper::insert);
    }

    /**
     * 可用卷分页查询
     * @return
     */
    public List<BusinessRoll>  rollPage(String material, String thickness, Integer width, Long userId) {
        BusinessRoll roll = new BusinessRoll();
        roll.setMaterial(material);
        roll.setThickness(thickness);
        roll.setWidth(width);
        List<BusinessRoll> rolls = businessRollMapper.selectList(new QueryWrapper<>(roll).in("status", BusinessRoll.STATUS_AVAILABLE,
                BusinessRoll.STATUS_USING));
        if (userId != null) {
            Collections.sort(rolls, (r1, r2) -> {
                if (userId.equals(r1.getUserId())) {
                    return -1;
                }
                if (userId.equals(r2.getUserId())) {
                    return 1;
                }
                return r1.getStatus() - r2.getStatus();
            });
        }
        return rolls;
    }

    /**
     * <h2>上料操作</h2>
     * <ul>
     *     <li>判断是否可上料：状态为10</li>
     *     <li>执行上料</li>
     * </ul>
     * @param id 卷id
     */
    @Transactional
    public void rollPutUp(Long id, Long userId) {
        BusinessRoll roll = businessRollMapper.selectById(id);
        if (roll == null) {
            throw new ResourceNotFoundException();
        }
        if (!BusinessRoll.STATUS_AVAILABLE.equals(roll.getStatus())) {
            throw new DataStateNotAllowedException();
        }
        BusinessRoll rollUpdate = new BusinessRoll();
        rollUpdate.setUpdateTime(DateUtil.now());
        rollUpdate.setId(id);
        rollUpdate.setStatus(BusinessRoll.STATUS_USING);
        rollUpdate.setUserId(userId);
        businessRollMapper.updateById(rollUpdate);
    }

    /**
     * <h2>剪板操作</h2>
     * <ul>
     *     <li>判断是否可剪：1.状态为20；2.操作用户为当前用户</li>
     *     <li>判断输入的剩余重量是否有效：不能大于当前重量</li>
     *     <li>执行剪板</li>
     * </ul>
     */
    @Transactional
    public void rollCut(Long id, BigDecimal weight, Long userId) {
        BusinessRoll roll = businessRollMapper.selectById(id);
        if (roll == null) {
            throw new ResourceNotFoundException();
        }
        if (!BusinessRoll.STATUS_USING.equals(roll.getStatus()) || !userId.equals(roll.getUserId())) {
            throw new DataStateNotAllowedException();
        }
        if (weight.compareTo(BigDecimal.ZERO) < 0 || weight.compareTo(roll.getBalanceWeight()) > 0) {
            throw new BaseRuntimeException("剩余重量不能小于0或者大于卷当前重量", HttpStatus.BAD_REQUEST);
        }
        BusinessRoll rollUpdate = new BusinessRoll();
        rollUpdate.setBalanceWeight(weight);
        rollUpdate.setId(id);
        rollUpdate.setUpdateTime(DateUtil.now());
        businessRollMapper.updateById(rollUpdate);
    }

    /**
     * <h2>退料操作</h2>
     * <ul>
     *     <li>判断是否可剪：1.状态为20；2.操作用户为当前用户</li>
     *     <li>执行退料</li>
     * </ul>
     */
    @Transactional
    public void rollWithdraw(Long id, BigDecimal weight, Long userId) {
        BusinessRoll roll = businessRollMapper.selectById(id);
        if (roll == null) {
            throw new ResourceNotFoundException();
        }
        if (!BusinessRoll.STATUS_USING.equals(roll.getStatus()) || !userId.equals(roll.getUserId())) {
            throw new DataStateNotAllowedException();
        }
        if (weight.compareTo(BigDecimal.ZERO) < 0 || weight.compareTo(roll.getBalanceWeight()) > 0) {
            throw new BaseRuntimeException("剩余重量不能小于0或者大于卷当前重量", HttpStatus.BAD_REQUEST);
        }
        BusinessRoll rollUpdate = new BusinessRoll();
        rollUpdate.setId(id);
        rollUpdate.setUpdateTime(DateUtil.now());
        rollUpdate.setBalanceWeight(weight);
        rollUpdate.setStatus(weight.equals(0) ? BusinessRoll.STATUS_RUN_OUT : BusinessRoll.STATUS_AVAILABLE);
        businessRollMapper.updateById(rollUpdate);
    }

    /**
     * <h2>返回待产</h2>
     * <p>
     *     <ul>
     *         <li>查询生产数据，判断是否满足条件（属于当前用户，状态为生产中），不满足则忽略单条数据</li>
     *         <li>已产为0-直接修改状态；已产大于0，拆分订单项后再修改状态</li>
     *     </ul>
     * </p>
     */
    @Transactional
    public void turnover(TurnoverParameter parameter, Long userId) {
        Map<Long, Integer> idQuantityMap = parameter.getItems().stream().collect(
                Collectors.toMap(TurnoverParameter.TurnoverItem::getId, TurnoverParameter.TurnoverItem::getProducedQuantity)
        );
        ProduceItem itemSelect = new ProduceItem();
        itemSelect.setStatus(ProduceItem.STATUS_PRODUCING);
        itemSelect.setUserId(userId);
        List<ProduceItem> items = produceItemMapper.selectList(
                new QueryWrapper<>(itemSelect).in("id", idQuantityMap.keySet())
        );
        for (ProduceItem item : items) {
            subItem(item,  idQuantityMap.get(item.getId()));
        }
    }

    /**
     * 订单项完成生产
     * @param item
     * @param producedQuantity
     */
    private void subItem(ProduceItem item, Integer producedQuantity) {
        if (producedQuantity > item.getQuantity()) {
            throw new BaseRuntimeException("已生产数量不能大于待生产数量", HttpStatus.BAD_REQUEST);
        }
        LocalDateTime now = DateUtil.now();
        if (producedQuantity.equals(0) || producedQuantity.equals(item.getQuantity())) {
            ProduceItem itemUpdate = new ProduceItem();
            itemUpdate.setId(item.getId());
            itemUpdate.setUpdateTime(now);
            itemUpdate.setStatus(producedQuantity.equals(0) ? ProduceItem.STATUS_TO_BE_PRODUCED
                    : ProduceItem.STATUS_PRODUCED);
            produceItemMapper.updateById(itemUpdate);
            BusinessOrderItem orderItemUpdate = new BusinessOrderItem();
            orderItemUpdate.setId(item.getId());
            orderItemUpdate.setUpdateTime(now);
            orderItemUpdate.setStatus(producedQuantity.equals(0) ? BusinessOrderItem.STATUS_TO_BE_PRODUCED
                    : BusinessOrderItem.STATUS_PRODUCED);
            businessOrderItemMapper.updateById(orderItemUpdate);
        } else {
            /**
             * 1.插入新生产数据，数量为待产，状态为待产；修改生产数据，状态为已产，数量为已产数量
             * 2.插入新订单项，数量为待产，状态为待产；修改订单项，状态为已产，数量为已产数量
             */
            Long producedId = item.getId(); //已产id
            BigDecimal producedWeight = item.getWeight().multiply(BigDecimal.valueOf(producedQuantity))
                    .divide(BigDecimal.valueOf(item.getQuantity()), 3, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            Long toBeProducedId = IdUtil.nextId(); //待产id
            Integer toBeProducedQuantity = item.getQuantity() - producedQuantity; //待产数量
            BigDecimal toBeProducedWeight = item.getWeight().subtract(producedWeight).setScale(2, RoundingMode.HALF_UP);

            item.setId(toBeProducedId);
            item.setStatus(ProduceItem.STATUS_TO_BE_PRODUCED);
            item.setQuantity(toBeProducedQuantity);
            item.setUpdateTime(now);
            item.setWeight(toBeProducedWeight);
            produceItemMapper.insert(item);
            ProduceItem itemUpdate = new ProduceItem();
            itemUpdate.setId(producedId);
            itemUpdate.setUpdateTime(now);
            itemUpdate.setStatus(ProduceItem.STATUS_PRODUCED);
            itemUpdate.setQuantity(producedQuantity);
            itemUpdate.setWeight(producedWeight);
            produceItemMapper.updateById(itemUpdate);
            BusinessOrderItem orderItem = businessOrderItemMapper.selectById(producedId);
            orderItem.setId(toBeProducedId);
            orderItem.setStatus(BusinessOrderItem.STATUS_TO_BE_PRODUCED);
            orderItem.setQuantity(toBeProducedQuantity);
            orderItem.setUpdateTime(now);
            orderItem.setWeight(toBeProducedWeight);
            businessOrderItemMapper.insert(orderItem);
            BusinessOrderItem orderItemUpdate = new BusinessOrderItem();
            orderItemUpdate.setId(producedId);
            orderItemUpdate.setUpdateTime(now);
            orderItemUpdate.setStatus(BusinessOrderItem.STATUS_PRODUCED);
            orderItemUpdate.setQuantity(producedQuantity);
            orderItemUpdate.setWeight(producedWeight);
            businessOrderItemMapper.updateById(orderItemUpdate);
        }

    }

    /**
     * 待打包工程列表
     * @param material
     * @param thickness
     * @param width
     * @return
     */
    public ToBePackageList shelfList(String material, String thickness, Integer width, Integer status, Long customerId) {
        ToBeProduceList toBeProduceList = produceList(material, thickness, width, status, customerId);
        ToBePackageList toBePackageList = new ToBePackageList();
        List<ToBePackageList.ProjectInfo> projectInfoList = toBeProduceList.getProjects().stream().map(p -> {
            ToBePackageList.ProjectInfo projectInfo = new ToBePackageList.ProjectInfo();
            projectInfo.setProjectName(p.getProjectName());
            projectInfo.setProjectVirtualId(p.getProjectVirtualId());
            projectInfo.setProjectBatchNumber(p.getProjectBatchNumber());
            projectInfo.setWeight(p.getWeight());
            projectInfo.setCustomerNickname(p.getCustomerNickname());
            return projectInfo;
        }).collect(Collectors.toList());
        toBePackageList.getProjects().addAll(projectInfoList);
        Map<String, ToBePackageList.ProjectInfo> virtualIdToProjectMap = projectInfoList.stream()
                .collect(Collectors.toMap(ToBePackageList.ProjectInfo::getProjectVirtualId, p -> p));
        if (!virtualIdToProjectMap.isEmpty()) {
            List<ProduceShelf> produceShelves = produceShelfMapper.selectList(new QueryWrapper<ProduceShelf>()
                    .eq("status", status)
                    .in("concat(project_id, '_', batch_number)", virtualIdToProjectMap.keySet()));
            produceShelves.stream().forEach(shelf -> {
                ToBePackageList.ProjectInfo pi = virtualIdToProjectMap.get(projectVirtualKey(shelf));
                if (pi != null) {
                    pi.getShelfList().add(shelf);
                }
            });
        }
        return toBePackageList;
    }

    /**
     * 工程可添加托盘的生产项列表
     * @param projectVirtualId
     * @return
     */
    public List<ProduceItem> leftItems(String projectVirtualId) {
        String[] strings = projectVirtualId.split("_");
        String projectId = strings[0];
        String batchNumber = strings[1];
        ProduceItem produceItem = new ProduceItem();
        produceItem.setProjectBatchNumber(batchNumber);
        produceItem.setProjectId(Long.parseLong(projectId));
        produceItem.setStatus(ProduceItem.STATUS_PRODUCED);
        return produceItemMapper.selectList(new QueryWrapper<>(produceItem).isNull("shelf_id"));
    }

    /**
     * 添加托盘
     */
    @Transactional
    public ProduceShelf shelfAdd(String projectVirtualId) {
        String[] strings = projectVirtualId.split("_");
        String projectId = strings[0];
        String batchNumber = strings[1];
        BusinessProject project = businessProjectMapper.selectById(projectId);
        ProduceShelf shelf = new ProduceShelf();
        shelf.setProjectId(Long.parseLong(projectId));
        shelf.setBatchNumber(batchNumber);
        int shelfIndex = produceShelfMapper.selectCount(new QueryWrapper<>(shelf)) + 1;
        shelf.setShelfIndex(shelfIndex);
        shelf.setProjectName(project.getName());
        shelf.setCustomerId(project.getCustomerId());
        shelf.setCreateTime(DateUtil.now());
        shelf.setStatus(ProduceItem.STATUS_PRODUCED);
        shelf.setShelfWeight(BigDecimal.ZERO);
        shelf.setTotalWeight(BigDecimal.ZERO);
        produceShelfMapper.insert(shelf);
        return shelf;
    }

    /**
     * 修改托盘
     * @param parameter
     * @return
     */
    @Transactional
    public ProduceShelf shelfModify(ShelfModifyParameter parameter) {
//        if (parameter.getTotalWeight() == null || parameter.getTotalWeight().compareTo(parameter.getShelfWeight()) < 0) {
//            parameter.setTotalWeight(parameter.getShelfWeight());
//        }
        ProduceShelf shelf = produceShelfMapper.selectById(parameter.getId());
        if (!ProduceItem.STATUS_PRODUCED.equals(shelf.getStatus())) {
            throw new BaseRuntimeException("托盘当前状态不允许修改，尝试重新获取客户端数据！", HttpStatus.BAD_REQUEST);
        }
        ProduceShelf shelfUpdate = new ProduceShelf();
        shelfUpdate.setShelfType(parameter.getShelfType());
        shelfUpdate.setShelfWeight(parameter.getShelfWeight());
        shelfUpdate.setTotalWeight(parameter.getTotalWeight());
        shelfUpdate.setId(parameter.getId());
        shelfUpdate.setUpdateTime(DateUtil.now());
        produceShelfMapper.updateById(shelfUpdate);
        return produceShelfMapper.selectById(parameter.getId());
    }

    /**
     * 查询托盘拥有的生产项
     * @param shelfId
     * @return
     */
    public List<ProduceItem> findProduceItemByShelfId(Long shelfId) {
        ProduceItem produceItem = new ProduceItem();
        produceItem.setShelfId(shelfId);
        return produceItemMapper.selectList(new QueryWrapper<>(produceItem));
    }

    /**
     * 获取可添加进托盘的生产项
     * @param shelfId 托盘id
     * @return
     */
    public List<ProduceItem> findProduceItemAvailableForShelf(Long shelfId) {
        ProduceShelf shelf = produceShelfMapper.selectById(shelfId);
        ProduceItem produceItem = new ProduceItem();
        produceItem.setProjectBatchNumber(shelf.getBatchNumber());
        produceItem.setProjectId(shelf.getProjectId());
        produceItem.setStatus(ProduceItem.STATUS_PRODUCED);
        return produceItemMapper.selectList(new QueryWrapper<>(produceItem).isNull("shelf_id"));
    }

    /**
     * 添加生产项给托盘
     * @param parameter
     */
    @Transactional
    public void addItemsToShelf(ItemsAddToShelfParameter parameter) {
        List<ProduceItem> items = produceItemMapper.selectBatchIds(parameter.getItemIdList());
        Optional<ProduceItem> optional = items.stream().filter(item -> item.getShelfId() != null ||
                !ProduceItem.STATUS_PRODUCED.equals(item.getStatus())).findAny();
        if (optional.isPresent()) {
            throw new BaseRuntimeException("客户端数据过期，请重新进入页面！", HttpStatus.BAD_REQUEST);
        }
        ProduceItem produceItemUpdate = new ProduceItem();
        produceItemUpdate.setUpdateTime(DateUtil.now());
        produceItemUpdate.setShelfId(parameter.getShelfId());
        produceItemMapper.update(produceItemUpdate, new QueryWrapper<ProduceItem>().in("id", parameter.getItemIdList()));
        recalculateShelfWeight(parameter.getShelfId());
    }

    /**
     * 删减生产项给托盘
     * @param parameter
     */
    @Transactional
    public void removeItemsToShelf(ItemsAddToShelfParameter parameter) {
        List<ProduceItem> items = produceItemMapper.selectBatchIds(parameter.getItemIdList());
        Optional<ProduceItem> optional = items.stream().filter(item -> !parameter.getShelfId().equals(item.getShelfId()) ||
                !ProduceItem.STATUS_PRODUCED.equals(item.getStatus())).findAny();
        if (optional.isPresent()) {
            throw new BaseRuntimeException("客户端数据过期，请重新进入页面！", HttpStatus.BAD_REQUEST);
        }
        produceItemMapper.updateShelfIdNull(parameter.getItemIdList(), DateUtil.now());
        recalculateShelfWeight(parameter.getShelfId());

    }

    /**
     * 重新计算托盘重量
     * @param shelfId
     */
    private void recalculateShelfWeight(Long shelfId) {
        ProduceItem produceItemSelect = new ProduceItem();
        produceItemSelect.setShelfId(shelfId);
        AtomicReference<BigDecimal> totalWeight = new AtomicReference<>(BigDecimal.ZERO);
        produceItemMapper.selectList(new QueryWrapper<>(produceItemSelect)).stream().forEach(item -> {
            totalWeight.set(totalWeight.get().add(item.getWeight()));
        });
        ProduceShelf produceShelfUpdate = new ProduceShelf();
        produceShelfUpdate.setId(shelfId);
        produceShelfUpdate.setTotalWeight(totalWeight.get().add(
                produceShelfMapper.selectById(shelfId).getShelfWeight())
                .setScale(2, RoundingMode.HALF_UP)
        );
        produceShelfUpdate.setUpdateTime(DateUtil.now());
        produceShelfMapper.updateById(produceShelfUpdate);
    }

    /**
     * 打包完成，需要更新订单项表和生产数据表
     * @param id 托盘id
     */
    @Transactional
    public void packageComplete(Long id) {
        ProduceShelf produceShelf = produceShelfMapper.selectById(id);
        if (produceShelf == null) {
            throw new ResourceNotFoundException();
        }
        if (!ProduceItem.STATUS_PRODUCED.equals(produceShelf.getStatus())) {
            throw new DataStateNotAllowedException();
        }
        ProduceShelf shelfUpdate = new ProduceShelf();
        shelfUpdate.setUpdateTime(DateUtil.now());
        shelfUpdate.setId(id);
        shelfUpdate.setStatus(ProduceItem.STATUS_PACKAGED);
        produceShelfMapper.updateById(shelfUpdate);
        ProduceItem itemUpdate = new ProduceItem();
        itemUpdate.setShelfId(id);
        List<ProduceItem> produceItems = produceItemMapper.selectList(new QueryWrapper<>(itemUpdate).select("id", "order_id"));
        if (!produceItems.isEmpty()) {
            itemUpdate.setShelfId(null);
            itemUpdate.setStatus(ProduceItem.STATUS_PACKAGED);
            itemUpdate.setUpdateTime(DateUtil.now());
            produceItemMapper.update(itemUpdate, new QueryWrapper<ProduceItem>().eq("shelf_id", id));
            BusinessOrderItem orderItemUpdate = new BusinessOrderItem();
            orderItemUpdate.setStatus(ProduceItem.STATUS_PACKAGED);
            orderItemUpdate.setUpdateTime(DateUtil.now());
            businessOrderItemMapper.update(orderItemUpdate, new QueryWrapper<BusinessOrderItem>()
                    .in("id", produceItems.stream().map(ProduceItem::getId).collect(Collectors.toList())));
            produceItems.stream().map(ProduceItem::getOrderId).forEach(businessOrderMapper::updateStatus);
        }

    }

    /**
     * 执行合包
     * <ul>
     *     <li>1.修改托盘信息并删除无效托盘</li>
     *     <li>2.修改生产项的托盘id</li>
     * </ul>
     * @param parameter
     */
    @Transactional
    public void reduce(ReduceParameter parameter) {
        if (parameter.getFrom().contains(parameter.getTo())) {
            throw new BaseRuntimeException("合包托盘id传递错误，目的托盘id不能包含于被合包托盘id集合中！", HttpStatus.BAD_REQUEST);
        }
        List<ProduceShelf> shelfList = produceShelfMapper.selectBatchIds(parameter.getFrom());
        if (shelfList.stream().filter(e -> !ProduceItem.STATUS_PRODUCED.equals(e.getStatus())).findAny().isPresent()) {
            throw new BaseRuntimeException("合包托盘id传递错误，存在状态不允许合包的托盘！", HttpStatus.BAD_REQUEST);
        }
        ProduceShelf toShelf = produceShelfMapper.selectById(parameter.getTo());
        if (shelfList.size() != parameter.getFrom().size() || toShelf == null) {
            throw new BaseRuntimeException("存在无效托盘id，请检查客户端数据状态，尝试重新打开app！", HttpStatus.BAD_REQUEST);
        }
        if (!ProduceItem.STATUS_PRODUCED.equals(toShelf.getStatus())) {
            throw new BaseRuntimeException("合包托盘id传递错误，存在状态不允许合包的托盘！", HttpStatus.BAD_REQUEST);
        }
        BigDecimal totalWeight = parameter.getTotalWeight();
//        if (totalWeight == null) {
//            for (ProduceShelf shelf : shelfList) {
//                totalWeight = totalWeight.add(shelf.getTotalWeight().subtract(shelf.getShelfWeight()));
//            }
//            totalWeight = totalWeight.add(toShelf.getTotalWeight()).setScale(2);
//        }
        ProduceShelf shelfUpdate = new ProduceShelf();
        shelfUpdate.setUpdateTime(DateUtil.now());
        shelfUpdate.setTotalWeight(totalWeight);
        shelfUpdate.setId(parameter.getTo());
        produceShelfMapper.updateById(shelfUpdate);
        produceShelfMapper.deleteBatchIds(parameter.getFrom());
        ProduceItem itemUpdate = new ProduceItem();
        itemUpdate.setShelfId(parameter.getTo());
        itemUpdate.setUpdateTime(DateUtil.now());
        produceItemMapper.update(itemUpdate, new QueryWrapper<ProduceItem>().in("shelf_id", parameter.getFrom()));
    }

    /**
     * 执行改版
     * <ul>
     *     <li>1.判断是否可以改版</li>
     *     <li>2.执行改版：修改订单项和生产项</li>
     * </ul>
     * @param parameter
     */
    @Transactional
    public void change(ChangeSpecificationParameter parameter) {
        ProduceItem item = produceItemMapper.selectById(parameter.getId());
        if (item == null) {
            throw new ResourceNotFoundException();
        }
        if (!ProduceItem.STATUS_TO_BE_PRODUCED.equals(item.getStatus())) {
            throw new DataStateNotAllowedException();
        }
        ProduceItem itemUpdate = new ProduceItem();
        itemUpdate.setUpdateTime(DateUtil.now());
        itemUpdate.setMaterial(parameter.getMaterial());
        itemUpdate.setThickness(parameter.getThickness());
        itemUpdate.setWidth(parameter.getWidth());
        itemUpdate.setId(parameter.getId());
        itemUpdate.setChangeSpecification(true);
        produceItemMapper.updateById(itemUpdate);
        BusinessOrderItem orderItemUpdate = new BusinessOrderItem();
        orderItemUpdate.setId(parameter.getId());
        orderItemUpdate.setMaterialActual(parameter.getMaterial());
        orderItemUpdate.setThicknessActual(parameter.getThickness());
        orderItemUpdate.setWidthActual(parameter.getWidth());
        orderItemUpdate.setUpdateTime(DateUtil.now());
        orderItemUpdate.setChangeSpecification(true);
        businessOrderItemMapper.updateById(orderItemUpdate);
    }

    /**
     * <h2>订单项执行配板</h2>
     * <ul>
     *     <li>1.判断当前状态是否是10</li>
     *     <li>2.配单数量等于订单项数量，直接修改状态并减库存；否则拆分订单项</li>
     *     <li>3.减库存板库存</li>
     *     <li>4.修改订单状态</li>
     * </ul>
     * @param parameter
     */
    @Transactional
    public void match(MatchParameter parameter) {
        ProduceItem item = produceItemMapper.selectById(parameter.getId());
        if (item == null) {
            throw new ResourceNotFoundException();
        }
        if (!ProduceItem.STATUS_TO_BE_PRODUCED.equals(item.getStatus())) {
            throw new DataStateNotAllowedException();
        }
        if (parameter.getQuantity() > item.getQuantity()) {
            throw new BaseRuntimeException("配板数量不能大于订单项待产数量", HttpStatus.BAD_REQUEST);
        }
        BusinessBoard boardSelect = new BusinessBoard();
        boardSelect.setMaterial(parameter.getMaterial());
        boardSelect.setThickness(parameter.getThickness());
        boardSelect.setWidth(parameter.getWidth());
        boardSelect.setLength(parameter.getLength());
        List<BusinessBoard> boards = businessBoardMapper.selectList(new QueryWrapper<>(boardSelect));
        if (boards.isEmpty()) {
            throw new BaseRuntimeException("缺少对应规格和长度的库存板！", HttpStatus.BAD_REQUEST);
        }
        int totalQuantity = boards.stream().mapToInt(BusinessBoard::getQuantity).sum();
        if (totalQuantity < parameter.getQuantity()) {
            throw new BaseRuntimeException("对应规格和长度的库存板数量只有" + totalQuantity + "!", HttpStatus.BAD_REQUEST);
        }
        subItem(item, parameter.getQuantity());
        businessOrderMapper.updateStatus(item.getOrderId());
        Map<Long, Integer> boardIdToQuantityMap = new HashMap<>();
        int quantity = parameter.getQuantity();
        for (BusinessBoard businessBoard : boards) {
            if (businessBoard.getQuantity() >= quantity) {
                boardIdToQuantityMap.put(businessBoard.getId(), businessBoard.getQuantity() - quantity);
                break;
            } else {
                boardIdToQuantityMap.put(businessBoard.getId(),0);
                quantity -= businessBoard.getQuantity();
            }
        }
        for (Long boardId : boardIdToQuantityMap.keySet()) {
            BusinessBoard boardUpdate = new BusinessBoard();
            boardUpdate.setId(boardId);
            boardUpdate.setUpdateTime(DateUtil.now());
            boardUpdate.setQuantity(boardIdToQuantityMap.get(boardId));
            businessBoardMapper.updateById(boardUpdate);
        }
    }

    /**
     * 获取拥有制定状态订单项的客户列表
     * @param itemStatus
     * @return
     */
    public List<BusinessCustomer> customerList(Integer ... itemStatus) {
        List<BusinessCustomer> customerList = new LinkedList<>();
        List<ProduceItem> items = produceItemMapper.selectList(
                new QueryWrapper<ProduceItem>().in("status", Arrays.asList(itemStatus))
        );
        if (!items.isEmpty()) {
            List<BusinessProject> projectList = businessProjectMapper.selectBatchIds(
                    items.stream().map(ProduceItem::getProjectId).collect(Collectors.toSet()));
            if (!projectList.isEmpty()) {
                customerList = businessCustomerMapper.selectBatchIds(
                        projectList.stream().map(BusinessProject::getCustomerId).collect(Collectors.toSet()));
            }
        }
        return customerList;
    }

    /**
     * 可打包或已打包托盘列表
     * @param customerId
     * @return
     */
    public ToBePackageShelfList toBePackageShelfList(Long customerId, Integer status) {
        ToBePackageShelfList toBePackageShelfList = new ToBePackageShelfList();
        ProduceShelf shelfSelect = new ProduceShelf();
        shelfSelect.setCustomerId(customerId);
        List<ProduceShelf> shelfList = produceShelfMapper.selectList(new QueryWrapper<>(shelfSelect)
                .eq("status", status));
        if (!shelfList.isEmpty()) {
            List<ProduceItem> itemList = produceItemMapper.selectList(new QueryWrapper<ProduceItem>()
                    .in("shelf_id", shelfList.stream().map(ProduceShelf::getId).collect(Collectors.toList())));
            if (!itemList.isEmpty()) {
                Map<Long, ToBePackageShelfList.Shelf> idToShelfMap = new HashMap<>(shelfList.size() * 2 + 1);
                Map<Long, Set<Long>> idToProjectSetMap = new HashMap<>(shelfList.size() * 2 + 1);
                shelfList.stream().forEach(e -> {
                    ToBePackageShelfList.Shelf shelf = new ToBePackageShelfList.Shelf();
                    shelf.setShelfType(e.getShelfType());
                    shelf.setProjectQuantity(0);
                    shelf.setShelfWeight(e.getShelfWeight());
                    shelf.setTotalWeight(e.getTotalWeight());
                    shelf.setShelfIndex(e.getShelfIndex());
                    shelf.setId(e.getId());
                    shelf.setStatus(e.getStatus());
                    shelf.setTotalQuantity(0);
                    shelf.setProjectName(e.getProjectName());
                    shelf.setProjectBatchNumber(e.getBatchNumber());
                    idToShelfMap.put(shelf.getId(), shelf);
                    toBePackageShelfList.getShelfList().add(shelf);
                    idToProjectSetMap.put(shelf.getId(), new HashSet<>());
                });
                itemList.stream().forEach(item -> {
                    ToBePackageShelfList.Shelf shelf = idToShelfMap.get(item.getShelfId());
                    shelf.setTotalQuantity(shelf.getTotalQuantity() + item.getQuantity());
                    idToProjectSetMap.get(item.getShelfId()).add(item.getProjectId());
                });
                idToProjectSetMap.forEach((id, set) -> {
                    idToShelfMap.get(id).setProjectQuantity(set.size());
                });
            }
        }

        return toBePackageShelfList;
    }

    /**
     * 出标签
     * @param shelfId
     * @return
     */
    public TipDetail tipDetail(Long shelfId) {
        ProduceShelf shelf = produceShelfMapper.selectById(shelfId);
        if (shelf == null) {
            throw new ResourceNotFoundException();
        }
        TipDetail tipDetail = new TipDetail();
        tipDetail.setShelf(shelf);
        List<ProduceItem> items = produceItemMapper.selectList(new QueryWrapper<ProduceItem>().eq("shelf_id", shelfId));
        Map<String, TipDetail.ProjectBatch> virtualKeyMap = new HashMap<>();
        Set<String> customerNickNames = new HashSet<>();
        items.stream().forEach(item -> {
            customerNickNames.add(item.getCustomerNickname());
            String key = projectVirtualKey(item);
            TipDetail.ProjectBatch projectBatch = virtualKeyMap.get(key);
            if (projectBatch == null) {
                projectBatch = new TipDetail.ProjectBatch();
                virtualKeyMap.put(key, projectBatch);
                projectBatch.setProjectName(item.getProjectName());
                projectBatch.setProjectBatchNumber(item.getProjectBatchNumber());
            }
            TipDetail.MaterialList materialList = null;
            if (!projectBatch.getData().isEmpty()) {
                Optional<TipDetail.MaterialList> optional =
                        projectBatch.getData().stream().filter(e -> e.getMaterial().equals(item.getMaterial())).findAny();
                if (optional.isPresent()) {
                    materialList = optional.get();
                }
            }
            if (materialList == null) {
                materialList = new TipDetail.MaterialList();
                materialList.setMaterial(item.getMaterial());
                projectBatch.getData().add(materialList);
            }
            materialList.getItems().add(item);
        });
        tipDetail.setCustomerNickname(customerNickNames.stream().collect(Collectors.joining(" ")));
        tipDetail.getProjects().addAll(virtualKeyMap.values());
        return tipDetail;
    }
}
